<!doctype html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Android Retrace 日志解析工具</title>
    <style>
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }

      body {
        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
        background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
        min-height: 100vh;
        padding: 20px;
      }

      .container {
        max-width: 1200px;
        margin: 0 auto;
        background: white;
        border-radius: 15px;
        box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
        overflow: hidden;
      }

      .header {
        background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
        color: white;
        padding: 30px;
        text-align: center;
      }

      .header h1 {
        font-size: 2.5rem;
        margin-bottom: 10px;
        font-weight: 300;
      }

      .header p {
        font-size: 1.1rem;
        opacity: 0.9;
      }

      .main-content {
        padding: 40px;
      }

      .status-bar {
        display: flex;
        align-items: center;
        margin-bottom: 30px;
        padding: 15px 20px;
        border-radius: 10px;
        font-weight: 500;
      }

      .status-connected {
        background: #d4edda;
        color: #155724;
        border: 1px solid #c3e6cb;
      }

      .status-disconnected {
        background: #f8d7da;
        color: #721c24;
        border: 1px solid #f5c6cb;
      }

      .status-indicator {
        width: 12px;
        height: 12px;
        border-radius: 50%;
        margin-right: 10px;
      }

      .status-connected .status-indicator {
        background: #28a745;
      }

      .status-disconnected .status-indicator {
        background: #dc3545;
      }

      .upload-section {
        margin-bottom: 40px;
      }

      .section-title {
        font-size: 1.3rem;
        margin-bottom: 20px;
        color: #333;
        font-weight: 600;
      }

      .upload-area {
        border: 2px dashed #ddd;
        border-radius: 10px;
        /* padding: 40px; */
        text-align: center;
        transition: all 0.3s ease;
        cursor: pointer;
        background: #fafafa;
      }

      .upload-area:hover {
        border-color: #4facfe;
        background: #f0f8ff;
      }

      .upload-area.dragover {
        border-color: #4facfe;
        background: #e6f3ff;
      }

      .upload-icon {
        font-size: 3rem;
        color: #ddd;
        margin-bottom: 20px;
      }

      .upload-text {
        font-size: 1.1rem;
        color: #666;
        margin-bottom: 10px;
      }

      .upload-hint {
        font-size: 0.9rem;
        color: #999;
      }

      .file-input {
        display: none;
      }

      .file-info {
        margin-top: 20px;
        padding: 15px;
        background: #e8f5e8;
        border-radius: 8px;
        border-left: 4px solid #28a745;
      }

      .file-name {
        font-weight: 600;
        color: #155724;
      }

      .stack-section {
        margin-bottom: 40px;
      }

      .stack-section-header {
        display: flex;
        align-items: center;
        gap: 16px;
        margin-bottom: 10px;
      }

      .stack-textarea {
        width: 100%;
        height: 200px;
        padding: 15px;
        border: 1px solid #ddd;
        border-radius: 8px;
        font-family: 'Courier New', monospace;
        font-size: 14px;
        resize: vertical;
        background: #f8f9fa;
      }

      .stack-textarea:focus {
        outline: none;
        border-color: #4facfe;
        box-shadow: 0 0 0 3px rgba(79, 172, 254, 0.1);
      }

      .btn {
        background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
        color: white;
        border: none;
        padding: 12px 30px;
        border-radius: 25px;
        font-size: 1rem;
        font-weight: 600;
        cursor: pointer;
        transition: all 0.3s ease;
        text-transform: uppercase;
        letter-spacing: 0.5px;
      }

      .btn:hover {
        transform: translateY(-2px);
        box-shadow: 0 10px 20px rgba(79, 172, 254, 0.3);
      }

      .btn:disabled {
        background: #ccc;
        cursor: not-allowed;
        transform: none;
        box-shadow: none;
      }

      .result-section {
        margin-top: 40px;
      }

      .result-content {
        background: #f8f9fa;
        border: 1px solid #e9ecef;
        border-radius: 8px;
        padding: 20px;
        font-family: 'Courier New', monospace;
        font-size: 14px;
        line-height: 1.6;
        white-space: pre-wrap;
        max-height: 400px;
        overflow-y: auto;
      }

      .loading {
        display: inline-block;
        width: 20px;
        height: 20px;
        border: 3px solid #f3f3f3;
        border-top: 3px solid #4facfe;
        border-radius: 50%;
        animation: spin 1s linear infinite;
        margin-right: 10px;
      }

      @keyframes spin {
        0% {
          transform: rotate(0deg);
        }
        100% {
          transform: rotate(360deg);
        }
      }

      .error {
        color: #dc3545;
        background: #f8d7da;
        border: 1px solid #f5c6cb;
        padding: 15px;
        border-radius: 8px;
        margin-top: 20px;
      }

      .success {
        color: #155724;
        background: #d4edda;
        border: 1px solid #c3e6cb;
        padding: 15px;
        border-radius: 8px;
        margin-top: 20px;
      }
    </style>
    <script src="/vue.global.prod.js"></script>
  </head>
  <body>
    <div id="app">
      <div class="container">
        <div class="header">
          <h1>Android Retrace 日志解析工具</h1>
          <p>上传mapping文件，输入异常堆栈，一键解析日志信息</p>
        </div>

        <div class="main-content">
          <!-- 连接状态 -->
          <div class="status-bar" :class="isConnected ? 'status-connected' : 'status-disconnected'">
            <div class="status-indicator"></div>
            {{ isConnected ? '已连接到服务器' : '未连接到服务器' }}
          </div>

          <!-- 文件上传区域 -->
          <div class="upload-section">
            <h2 class="section-title">1. 上传 Mapping 文件</h2>
            <div class="upload-area" :class="{ 'dragover': isDragOver }" @click="triggerFileInput" @dragover.prevent="isDragOver = true" @dragleave.prevent="isDragOver = false" @drop.prevent="handleFileDrop">
              <div class="upload-icon">📁</div>
              <div class="upload-text">{{ selectedFile ? '点击或拖拽更换文件' : '点击或拖拽上传文件' }}</div>
              <div class="upload-hint">支持 .txt, .mapping 等格式</div>
              <input type="file" ref="fileInput" class="file-input" @change="handleFileSelect" accept=".txt,.mapping" />
            </div>

            <div v-if="selectedFile" class="file-info">
              <div class="file-name">📄 {{ selectedFile.name }}</div>
              <div style="margin-top: 5px; color: #666">大小: {{ formatFileSize(selectedFile.size) }}</div>
            </div>
          </div>

          <!-- 堆栈信息输入 -->
          <div class="stack-section">
            <div class="stack-section-header">
              <h2 class="section-title" style="margin-bottom: 0">2. 输入异常堆栈信息</h2>
              <span style="color: #dc3545; font-weight: 400">（可解析的堆栈需 at 开头）</span>
            </div>
            <textarea v-model="stackInfo" class="stack-textarea" placeholder="请粘贴异常堆栈信息，例如：&#10;java.lang.RuntimeException: Unable to start activity&#10;    at com.example.app.MainActivity.onCreate(MainActivity.java:25)&#10;    at android.app.Activity.performCreate(Activity.java:7136)&#10;    ..."> </textarea>
          </div>

          <!-- 解析按钮 -->
          <div style="text-align: center; margin: 30px 0; display: flex; justify-content: center; gap: 20px">
            <button class="btn" @click="parseLog" :disabled="!canParse">
              <span v-if="isParsing" class="loading"></span>
              {{ isParsing ? '解析中...' : '开始解析' }}
            </button>
            <button class="btn" style="background: #f8d7da; color: #721c24" @click="clearInput" :disabled="!canClear">清空输入</button>
          </div>

          <!-- 解析结果 -->
          <div v-if="parseResult || errorMessage" class="result-section">
            <h2 class="section-title">3. 解析结果</h2>
            <div v-if="errorMessage" class="error">{{ errorMessage }}</div>
            <div v-else-if="parseResult" class="result-content">{{ parseResult }}</div>
          </div>
        </div>
      </div>
    </div>

    <script>
      const { createApp } = Vue

      createApp({
        data() {
          return {
            isConnected: false,
            ws: null,
            token: null,
            selectedFile: null,
            stackInfo: '',
            parseResult: '',
            errorMessage: '',
            isParsing: false,
            isDragOver: false,
          }
        },
        computed: {
          canParse() {
            return this.selectedFile && this.stackInfo.trim() && this.isConnected && !this.isParsing
          },
          canClear() {
            return !!this.stackInfo || !!this.parseResult || !!this.errorMessage
          },
        },
        mounted() {
          this.connectWebSocket()
        },
        beforeUnmount() {
          if (this.heartbeatInterval) clearInterval(this.heartbeatInterval)
          if (this.ws) this.ws.close()
        },
        methods: {
          connectWebSocket() {
            const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
            //const wsUrl = `${protocol}//127.0.0.1:8080/ws`;
            const wsUrl = `${protocol}//${window.location.host}/ws`
            this.ws = new WebSocket(wsUrl)

            this.ws.onopen = () => {
              this.isConnected = true
              // 连接建立后，等待后端发送 token
            }

            this.ws.onmessage = (event) => {
              const msg = JSON.parse(event.data)
              console.log('收到WebSocket消息:', msg)
              if (msg.type === 'token') {
                this.token = msg.data
                document.cookie = `token=${this.token};path=/;`

                this.heartbeatInterval = setInterval(() => {
                  if (this.ws && this.ws.readyState === WebSocket.OPEN) {
                    console.log('发送心跳')
                    this.ws.send(JSON.stringify({ type: 'heartbeat' }))
                  }
                }, 30000)
              }
              // ...处理其它消息...
            }

            this.ws.onclose = () => {
              this.isConnected = false
              this.token = null
            }
          },

          generateToken() {
            // 生成一个简单的token
            this.token = 'token_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9)
            console.log('生成token:', this.token)
          },

          getTokenFromCookie() {
            const match = document.cookie.match(/(?:^|;\s*)token=([^;]*)/)
            return match ? match[1] : ''
          },

          handleWebSocketMessage(message) {
            switch (message.type) {
              case 'upload_success':
                this.errorMessage = ''
                break
              case 'parse_success':
                this.parseResult = message.data.result
                this.errorMessage = ''
                break
              case 'error':
                this.errorMessage = message.data.message
                break
            }
          },

          triggerFileInput() {
            this.$refs.fileInput.click()
          },

          handleFileSelect(event) {
            const file = event.target.files[0]
            if (file) {
              this.selectedFile = file
              this.uploadFile()
            }
          },

          handleFileDrop(event) {
            this.isDragOver = false
            const file = event.dataTransfer.files[0]
            if (file) {
              this.selectedFile = file
              this.uploadFile()
            }
          },

          async uploadFile() {
            const token = this.getTokenFromCookie()
            if (!this.selectedFile) return

            if (!token) {
              this.errorMessage = '请等待连接建立完成'
              return
            }

            const formData = new FormData()
            formData.append('file', this.selectedFile)

            try {
              const response = await fetch(`/upload?token=${token}`, {
                method: 'POST',
                body: formData,
              })

              const result = await response.json()
              if (result.success) {
                this.errorMessage = ''
                console.log('文件上传成功:', result)
                // 通过WebSocket通知后端
                if (this.ws && this.ws.readyState === WebSocket.OPEN) {
                  this.ws.send(
                    JSON.stringify({
                      type: 'file_uploaded',
                      data: {
                        token: token,
                        filename: this.selectedFile.name,
                      },
                    })
                  )
                }
              } else {
                this.errorMessage = result.message
              }
            } catch (error) {
              this.errorMessage = '文件上传失败: ' + error.message
            }
          },

          async parseLog() {
            const token = this.getTokenFromCookie()
            if (!this.canParse) return

            this.isParsing = true
            this.errorMessage = ''
            this.parseResult = ''

            try {
              const response = await fetch('/parse', {
                method: 'POST',
                headers: {
                  'Content-Type': 'application/json',
                },
                body: JSON.stringify({
                  token: token,
                  stackInfo: this.stackInfo,
                }),
              })

              const result = await response.json()
              if (result.success) {
                this.parseResult = result.result
                this.errorMessage = ''
                // 通过WebSocket通知后端解析完成
                if (this.ws && this.ws.readyState === WebSocket.OPEN) {
                  this.ws.send(
                    JSON.stringify({
                      type: 'parse_completed',
                      data: { token: token, result: result.result },
                    })
                  )
                }
              } else {
                this.errorMessage = result.message
              }
            } catch (error) {
              this.errorMessage = '解析失败: ' + error.message
            } finally {
              this.isParsing = false
            }
          },

          formatFileSize(bytes) {
            if (bytes === 0) return '0 Bytes'
            const k = 1024
            const sizes = ['Bytes', 'KB', 'MB', 'GB']
            const i = Math.floor(Math.log(bytes) / Math.log(k))
            return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
          },

          clearInput() {
            this.stackInfo = ''
            this.parseResult = ''
            this.errorMessage = ''
          },
        },
      }).mount('#app')
    </script>
  </body>
</html>
